/******************************************************************************

 @file CUI.h

 @brief 此文件包含通用用户接口(Common User Interface, CUI)的接口定义

 Group: LPRF SW RND
 Target Device: cc13xx_cc26xx

 ******************************************************************************
 
 Copyright (c) 2016-2025, Texas Instruments Incorporated
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 *  Redistributions of source code must retain the above copyright
    notice, this list of conditions and the following disclaimer.

 *  Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.

 *  Neither the name of Texas Instruments Incorporated nor the names of
    its contributors may be used to endorse or promote products derived
    from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 ******************************************************************************
 
 
 *****************************************************************************/

/**
@headerfile  cui.h
$Date: 2018-12-18 12:13:13 -0800 (Tue, 18 Dec 2018) $
$Revision: 42528 $

@mainpage CUI API 文档

概述
============================

    此文件包含通用用户接口(Common User Interface)或CUI的接口。

    CUI控制对用户接口资源的访问。在launchpad设备上，这些资源由按钮(Buttons)、LED和UART输入/输出组成。

    CUI提供了一个API，可用于请求访问资源，然后对这些资源进行读/写操作。为了请求和获取资源，使用了'client'(客户端)的概念。
    客户端句柄用于与CUI API成功通信。一旦您使用CUI打开客户端，您就可以请求访问资源。如果资源可用，CUI将给予客户端对该资源的访问权限。

资源类型
============================

    如前所述，CUI控制访问的三种不同资源：(按钮、LED和UART输入/输出)。

    对于按钮和LED，请求和使用资源的过程相当简单，几乎与您使用Button或LED驱动程序执行相同操作的方式相同。

    对于UART输入/输出，需要考虑两种资源。首先是'menu'(菜单)的概念。特定于您应用程序的菜单可以在CUI中注册。
    CUI将处理菜单导航、读取和写入。菜单可以按需要简单或复杂。第二个UART输入/输出是'status line'(状态行)的概念。
    菜单设计为由用户输入驱动的显示，而状态行能够显示开发者选择的任何特定信息集的最新值。
    菜单项的一个很好的例子是您的设备可以执行的任何类型的LED切换事件，或者可能是调试操作，状态行的一个例子可以是当前网络状态或任何应用程序状态信息。
    关于菜单和状态行的更多详细信息可以在下面找到。

使用方法
==========================

    此文档提供了基本使用摘要和一组以注释代码片段形式出现的示例。API的详细描述在后续章节中提供。

    初始化是第一步。

    @code
    // 导入CUI定义
    #include "ti/common/cc26xx/cui/cui.h"

    CUI_params_t cuiParams;
    CUI_paramsInit(&cuiParams);

    // CUI的一次性初始化
    if (CUI_SUCCESS != CUI_init(&cuiParams)) {
        // 处理失败
    }
    @endcode

    通过调用CUI_paramsInit()，您将cuiParams设置为其默认值。

        cuiParams.manageBtns = true
        cuiParams.manageLeds = true
        cuiParams.manageUart = true

    如果您的应用程序需要对按钮、LED或UART进行特殊管理，开发者可以在调用CUI_init()之前适当设置这些参数。

    CUI初始化后，您就可以打开客户端。

    @code
    CUI_clientParams_t clientParams;
    CUI_clientParamsInit(&clientParams);

    strncpy(clientParams.clientName, "My first CUI application", MAX_CLIENT_NAME_LEN);
    clientParams.maxStatusLines = 5;

    CUI_clientHandle_t clientHandle = CUI_clientOpen(&clientParams);
    if (clientHandle == NULL) {
        // 处理失败
    }
    @endcode

    通过调用CUI_clientParamsInit()，您将clientParams设置为其默认值。

        strcpy(clientParams.clientName, "");
        clientParams.maxStatusLines = 0;

    <b>目前要求您的clientName不能为空。</b>
    它可以是您想要的任何内容，因为它仅用于创建简单的哈希值。

    如果您想在应用程序中添加状态行，您需要更新clientParams.maxStatusLines，如上面的代码片段所示。

    现在您有了有效的客户端，您可以开始请求访问资源。当请求按钮资源时，您必须指定您请求的按钮以及CUI可以用来通知您按钮更改事件的回调函数，
    该回调函数必须匹配CUI_btnPressCB_t原型。

    @code
    CUI_btnRequest_t rightBtnReq;
    rightBtnReq.index = CONFIG_BTN_RIGHT;
    rightBtnReq.appCB = myBtnChangeHandler;

    if (CUI_SUCCESS != CUI_btnResourceRequest(clientHandle, &rightBtnReq) {
        //处理失败
    }
    @endcode

    如果资源可用于获取，那么您现在就可以访问右侧按钮。

    或者，如果您想读取按钮状态而不需要应用程序回调，您可以通过使用NULL appCB以轮询模式请求按钮。
    然后只需在需要时请求按钮的值。

    @code
    CUI_btnRequest_t rightBtnReq;
    rightBtnReq.index = CONFIG_BTN_RIGHT;
    rightBtnReq.appCB = NULL;

    if (CUI_SUCCESS != CUI_btnResourceRequest(clientHandle, &rightBtnReq) {
        //处理失败
    }

    bool btnState = false;
    retVal = CUI_btnGetValue(clientHandle, CONFIG_BTN_RIGHT, &btnState);
    if (!btnState) {
        //处理按钮逻辑
    }
    @endcode

    如果您想更改已访问按钮的btnMode，您可以使用CUI_btnSetCb() API。
    通过为第三个参数使用非NULL值，您将使用某种回调模式。如果您为第三个参数使用NULL值，您将使用轮询模式。

    @code
    CUI_retVal retVal;
    retVal = CUI_btnSetCb(clientHandle, CONFIG_BTN_RIGHT, appChangeKeyCB);
    if (retVal != CUI_SUCCESS) {
        //处理失败
    }
    @endcode

    按钮很好，但只是图片的一部分。让我们也请求一个LED。
    这次您需要提供的唯一信息是您希望获取的LED的索引。

    @code
    CUI_ledRequest_t greenLedReq;
    greenLedReq.index = CONFIG_LED_GREEN;
    if (CUI_SUCCESS != CUI_ledResourceRequest(clientHandle, &greenLedReq) {
        // 处理失败
    }
    @endcode

    如果LED可用，您将获得对它的访问权限，当您希望更改LED状态时，只需调用CUI LED API。

    @code
    CUI_ledToggle(clientHandle, CONFIG_LED_GREEN);
    CUI_ledBlink(clientHandle, CONFIG_LED_GREEN, 5);
    CUI_ledBlink(clientHandle, CONFIG_LED_GREEN, CUI_BLINK_CONTINUOUS);
    CUI_ledOn(clientHandle, CONFIG_LED_GREEN);
    CUI_ledOff(clientHandle, CONFIG_LED_GREEN);
    @endcode

    关于CUI_ledBlink的一个注意事项是，您可以选择连续闪烁或选择闪烁特定次数。如果在LED已经在闪烁时调用CUI_ledBlink，
    唯一会发生的事情是闪烁次数将重置为新值。

    现在您完全控制按钮和LED。但您可能想让用户知道您何时加入了网络，例如。这是状态行的完美用途。
    首先，我们必须请求访问可用的状态行。状态行按请求按数字顺序分发，您可用的状态行数量由打开客户端时的clientParams.maxStatusLines决定。

    @code
    uint32_t connStatusLine;
    if (CUI_SUCCESS != CUI_statusLineResourceRequest(clientHandle, "Conn Status", &connStatusLine) {
        // 处理失败
    }
    @endcode

    如果clientParams.maxStatusLines还没有从CUI请求，您将获得对该行的访问权限。当获取状态行时，将打印默认行值，格式为"LABEL: --"。
    因此在上面的示例中，我们将在屏幕上看到"Conn Status: --"。

    如果您想告诉用户该状态行的不同值，您需要告诉CUI新值是什么。使用可变参数函数，您可以传入格式字符串和任意数量的可选项目，
    这些项目将根据您的格式字符串进行格式化。如果您熟悉的话，类似于printf()。

    @code
    char lineFormat[MAX_STATUS_LINE_VALUE_LEN];
    strncpy(lineFormat, "Successfully Connected", MAX_STATUS_LINE_VALUE_LEN);
    if (CUI_SUCCESS != CUI_statusLinePrintf(clientHandle, connStatusLine, lineFormat) {
        //处理失败
    }
    @endcode

    现在您将看到"Conn Status: Successfully Connected"。

    如果您想让状态行更有趣一些，默认情况下有一些颜色代码可供您使用。要使用这些，只需在颜色宏和重置宏之间包围您希望着色的文本。

    @code
    CUI_COLOR_RESET
    CUI_COLOR_RED
    CUI_COLOR_GREEN
    CUI_COLOR_YELLOW
    CUI_COLOR_MAGENTA
    CUI_COLOR_CYAN
    CUI_COLOR_WHITE
    @endcode

    例如，您可以使用红色表示不好的东西，绿色表示好的东西。这些可用于状态行或菜单行。任何正在显示的文本都可以用这些宏着色。

    @code
    CUI_statusLinePrintf(clientHandle, connStatusLine, CUI_COLOR_RED "Connection Failure :(" CUI_COLOR_RESET);

    CUI_statusLinePrintf(clinetHandle, connStatusLine, CUI_COLOR_GREEN "Connection Successful! :)" CUI_COLOR_RESET);
    @endcode

    最后，为了创建菜单，您必须首先声明菜单布局。菜单可以按您想要的简单或复杂。不过，每个应用程序菜单的根都有三件事。
        主菜单
        子菜单
        菜单操作

    主菜单顾名思义是您应用程序的顶级菜单，这是您将用来向CUI注册整个菜单的内容。您的主菜单可以包含任意数量的子菜单和菜单操作。
    反过来，这些子菜单也可以包含任意数量的子菜单和菜单操作。这创建了一个树形拓扑，将按您希望的程度进行。

    菜单操作可以有两种变体。普通操作或可拦截操作。普通操作最适合通过UART输入触发代码段。
    例如，假设您想切换LED状态或触发设备连接到开放网络。那么普通操作正是您想要的。
    您可能有几个想要执行的非常相似的操作。普通操作可以通过为您提供调用您操作的itemEntry来帮助解决这个问题。
    这可以用于在代码中拥有单个操作函数，该函数可以根据接收到的itemEntry处理多种不同情况。
    不过，如果您想做一些更复杂的事情，比如修改设备的通道掩码或panId，则需要可拦截操作。

    可拦截操作的创建方式不是触发一次性代码段，而是实际开始拦截来自UART的输入。
    例如，假设您想修改通道掩码。用户选择操作，然后所有UART输入都可以由您的操作代码处理。
    您可以按您想要的方式处理此输入。然后当用户取消选择操作时，UART输入将再次由CUI处理，菜单导航将正常继续。

    由于菜单可能变得相当复杂，此文件提供了一些有用的宏来简化菜单定义。以下是声明主菜单的示例，该主菜单具有普通操作和子菜单。
    在子菜单中有一个可拦截操作。

    @code
    CUI_SUB_MENU(mySubMenu, " My Sub Menu ", 1, myMainMenu)
        CUI_MENU_ITEM_INT_ACTION("< Edit Channel Mask >", editChannelMaskFn)
    CUI_SUB_MENU_END

    CUI_MAIN_MENU(myMainMenu, " My Main Menu ", 2, processMenuUpdateFn)
        CUI_MENU_ITEM_ACTION("<  Toggle LED   >", toggleLedFn)
        CUI_MENU_ITEM_SUBMENU(&mySubMenu)
    CUI_MAIN_MENU_END
    @endcode

    如果您想知道宏是如何工作的，可以查看实现。以下是CUI_MAIN_MENU和CUI_SUB_MENU宏的原型。

    @code
        #define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn)
        #define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu)
    @endcode

    现在您可以看到子菜单被定义为'mySubMenu'符号，菜单标题为" My Sub Menu "，'1'个菜单项，其父菜单是'myMainMenu'符号。

    类似地，主菜单被声明为'myMainMenu'符号，菜单标题为" My Main Menu "和2个菜单项。不过，最后一个参数在它们之间是不同的，
    原因有两个。首先，主菜单没有父菜单的概念，因为它已经是顶级菜单。其次，'_pMenuUpdateFn'是一个参数，以便CUI可以在需要菜单处理时获得处理上下文。

    由于CUI不创建rtos任务，它在技术上没有自己的处理上下文。这意味着如果任何CUI菜单处理要发生，比如用户在不同菜单之间左右导航，
    其他rtos任务必须拥有处理时间才能发生。此函数只需要最终调用CUI_processMenuUpdate。
    无论是直接执行还是向信号量发布事件，以便任务在有时间时调用CUI_processMenuUpdate()，都由开发者决定。
    <b>要清楚，'_pMenuUpdateFn'是您的菜单正常运行所必需的。</b>没有它，菜单将无法注册。

    现在您已经定义了主菜单，它仍然需要在CUI中注册。

    @code
    if (CUI_SUCCESS != CUI_registerMenu(clientHandle, &myMainMenu) {
        //处理失败
    }
    @endcode

    只要不超过MAX_REGISTERED_MENUS已经注册，并且您的菜单定义正确，注册将成功，您的主菜单将出现在UART上。

    注意到如何能够注册MAX_REGISTERED_MENUS？
    这是一个功能，当两个示例合并在一起创建DMM（动态多协议管理器）示例时，每个独立应用程序菜单的菜单可以安全存在而不影响其他菜单。
    当注册多个菜单时，CUI将引入一个新的最顶层主菜单。这个主菜单然后将为每个向CUI注册的菜单有一个子菜单。
    这个新多菜单的标题将默认为" TI DMM Application "，但可以通过向CUI_updateMultiMenuTitle()函数提供不同有效c字符串的地址来更改。

    @code
    const char newTitle[] = " My First Multi Menu Project ";
    CUI_updateMultiMenuTitle(newTitle);
    @endcode
    @endcode

    在开发可拦截操作时，重要的是要知道期望接收什么作为输入。您的可拦截操作应该匹配CUI_pFnIntercept_t typedef。
        typedef void (*CUI_pFnIntercept_t)(const char _input, char* _lines[3], CUI_cursorInfo_t * _curInfo);
    第一个参数_input将是一个单字节，指示用户按下的输入类型。有一些特殊的输入类型供您使用。

    @code
    CUI_ITEM_PREVIEW
    CUI_ITEM_INTERCEPT_START
    CUI_ITEM_INTERCEPT_STOP
    CUI_ITEM_INTERCEPT_CANCEL
    CUI_INPUT_UP
    CUI_INPUT_DOWN
    CUI_INPUT_RIGHT
    CUI_INPUT_LEFT
    CUI_INPUT_BACK
    CUI_INPUT_ESC
    @endcode

    CUI_ITEM_PREVIEW允许您的操作在菜单上显示一些信息，而无需用户执行您的操作。这对于可配置的应用程序变量（如panId）很有用。
    当用户导航到理论上的"< PAN ID >"屏幕顶部时，它还可以显示当前panId是什么，而无需用户执行操作。
    在CUI_ITEM_PREVIEW事件的情况下，您的可拦截操作只能在第一行和第二行上提供预览。第三行将由cui维护为项目的描述，无论您在第三行中放置什么。

    CUI_ITEM_INTERCEPT_START是对您操作的指示，表明用户已决定执行您的操作。如果必要，您可以在这里为操作的生命周期执行某种设置步骤。

    CUI_ITEM_INTERCEPT_STOP是对您操作的指示，表明用户已完成在您操作中的时间。您可以在这里为操作结束执行一些清理代码或最终代码。

    CUI_ITEM_INTERCEPT_CANCEL是对您操作的指示，表明用户决定取消您操作中的任何当前进度。在这种情况下应该/可以做什么取决于您的操作在做什么。
    （即）如果您的操作正在修改Pan ID。CUI_ITEM_INTERCEPT_START可以清除pan ID的本地副本，用户可以修改本地副本。
    然后如果您收到CUI_ITEM_INTERCEPT_CANCEL输入，您应该清除本地副本并确保不保存用户所做的更改。

    CUI_ITEM_INTERCEPT_START和CUI_ITEM_INTERCEPT_STOP对于可配置变量（如panID）很有用，因为当CUI_ITEM_INTERCEPT_START发生时，
    您可以从堆栈获取最新的panId并在操作的生命周期内保存它的静态本地副本。然后当用户修改panId时，您只更新变量的静态本地版本。
    最后，当用户完成对panId的修改时，CUI_ITEM_INTERCEPT_STOP将通过，您可以将新的panId设置为堆栈中的官方panId。
    此过程允许您限制需要调用的堆栈API数量。

    UP、DOWN、RIGHT、LEFT输入直接来自用户键盘上的箭头键。类似地，BACK输入来自键盘上的退格键。与所有这些输入一样。
    作为可拦截操作的开发者，您可以选择是否对它们采取行动。它们只在您想使用它们时存在。

    除了这些特殊输入外，用户输入的任何ascii字符都将通过相同的_input变量提供给您。再次由您作为开发者决定如何处理此输入。
    还为您提供了一些更多的宏来简化您的逻辑。您可以使用这些来确定输入是否是您期望的。根据提供的_input，它们将返回TRUE或FALSE。
        CUI_IS_INPUT_NUM(_input)
        CUI_IS_INPUT_ALPHA(_input)
        CUI_IS_INPUT_ALPHA_NUM(_input)
        CUI_IS_INPUT_HEX(_input)
        CUI_IS_INPUT_BINARY(_input)

    CUI_pFnIntercept_t的第二个参数是菜单将打印的_pLines[3]。通过在这些缓冲区中放置c字符串，您可以在用户预览您的操作或
    在您操作的可拦截期间与用户共享任何信息。在向这些缓冲区写入数据时，注意每行不要写入超过MAX_MENU_LINE_LEN。
    这将导致未定义的行为，并可能导致您的应用程序断言。这些缓冲区是出于善意提供给您的，所以请给予应有的尊重，CUI将相应地表现 :)

    CUI_pFnIntercept_t的最后一个参数是指向CUI_cursorInfo_t结构的指针。这仅在您想使用它时存在。如果您不修改此结构，则不会改变任何行为。
    不过，如果您希望在菜单上显示光标以向用户指示某些信息在哪里，那么您可以设置cursor.col和cursor.row值。
    通过这样做，CUI将按照您的要求在屏幕上维护光标。无需关闭光标，因为在接收到CUI_ITEM_INTERCEPT_STOP输入后，
    CUI将自动移除光标。在CUI_ITEM_PREVIEW的情况下，光标将被忽略。这意味着光标只能在用户当前使用您的可拦截项目操作时出现在屏幕上。

--------------------------------------------------------------------------------
**/
#ifndef CUI_H
#define CUI_H

#include <stdlib.h>
#include <stdint.h>
#include <ti/drivers/apps/Button.h>
#include <ti/drivers/apps/LED.h>

#ifdef __cplusplus
extern "C"
{
#endif

/*********************************************************************
 * 宏定义
 */
/*
 * CUI模块可配置定义。用户可以使用这些来允许更多客户端打开或更改菜单行的最大长度。
 *
 * !!!!
 * !!!! 不要直接修改这些值。使用.opt文件或项目级定义
 * !!!!
 *
 * 通过增加这些值，cui模块将需要使用更多内存。
 * 因此，您可以通过减少这些值来减少此模块将需要的内存量。
 *
 * 每个客户端的RAM使用量         = ~56字节
 * 每个菜单的RAM使用量           = ~20字节
 * 每个状态行的RAM使用量         = ~76字节
 *
 * 减少MAX_*_LEN定义将减少RAM使用量，但减少的内存值不会缩放到客户端/菜单/状态行的数量。
 * 在大多数情况下，更改MAX_*_LEN定义不会产生任何明显的内存节省。
 */

/*
 * 客户端可配置定义
 */
#ifndef MAX_CLIENTS
#define MAX_CLIENTS                 2
#endif
#ifndef MAX_CLIENT_NAME_LEN
#define MAX_CLIENT_NAME_LEN         64
#endif


/*
 * 菜单可配置定义
 */
#ifndef MAX_REGISTERED_MENUS
#define MAX_REGISTERED_MENUS        4
#endif
#ifndef MAX_MENU_LINE_LEN
#define MAX_MENU_LINE_LEN           128
#endif

/*
 * 状态行可配置定义
 */
#ifndef MAX_STATUS_LINE_LABEL_LEN
#define MAX_STATUS_LINE_LABEL_LEN   32
#endif
#ifndef MAX_STATUS_LINE_VALUE_LEN
#define MAX_STATUS_LINE_VALUE_LEN   128
#endif

#ifndef CUI_MIN_FOOTPRINT
/*
 * 创建主菜单。主菜单必须具有非NULL的uart更新函数。
 * 这将在注册菜单时进行验证。numItems增加1以允许在菜单之间有一个通用的默认"Back"或"Help"菜单项。
 */
#define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn) \
        CUI_menu_t _menuSymbol = { \
        .uartUpdateFn=_pMenuUpdateFn, \
        .pTitle=_pMenuTitle, \
        .numItems=_numItems + 1, \
        .pUpper=NULL, \
            .menuItems = {
/*
 * 创建子菜单。这将在注册菜单时进行验证。numItems增加1以允许在菜单之间有一个通用的默认"Back"或"Help"菜单项。
 */
#define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu) \
        extern CUI_menu_t _pUpperMenu; \
        CUI_menu_t _menuSymbol = { \
        .uartUpdateFn=NULL, \
        .pTitle=_pMenuTitle, \
        .numItems=_numItems + 1, \
        .pUpper=&_pUpperMenu, \
            .menuItems = {

/*
 * 将_pSubMenu插入父菜单的.menuItems[]中。
 */
#define CUI_MENU_ITEM_SUBMENU(_pSubMenu) { \
        .pDesc=NULL, \
        .itemType=CUI_MENU_ITEM_TYPE_SUBMENU, \
        .item.pSubMenu=(&_pSubMenu)},

/*
 * 将操作插入父菜单的.menuItems[]中。
 */
#define CUI_MENU_ITEM_ACTION(_pItemDesc, _pFnAction) { \
        .pDesc=(_pItemDesc), \
        .itemType=CUI_MENU_ITEM_TYPE_ACTION, \
        .interceptActive=false, \
        .item.pFnAction=(_pFnAction)},

/*
 * 将可拦截操作插入父菜单的.menuItems[]中。
 */
#define CUI_MENU_ITEM_INT_ACTION(_pItemDesc, _pFnIntercept) { \
            .pDesc=(_pItemDesc), \
            .itemType=CUI_MENU_ITEM_TYPE_INTERCEPT, \
            .interceptActive=false, \
            .item.pFnIntercept=(_pFnIntercept)},

/*
 * 将列表操作插入父菜单的.menuItems[]中。
 */
#define CUI_MENU_ITEM_LIST_ACTION(_pItemDesc, _maxListItems, _pFnListAction) { \
            .pDesc=(_pItemDesc), \
            .itemType=CUI_MENU_ITEM_TYPE_LIST, \
            .interceptActive=false, \
            .item.pList=&((CUI_list_t){ \
                .pFnListAction=(_pFnListAction), \
                .maxListItems=_maxListItems, \
                .currListIndex=0})},

/*
 * 帮助宏，为所有菜单添加通用Help和Back屏幕
 * CUI将为您使用这些。不要在应用程序中使用这些。
 */
#define CUI_MENU_ITEM_HELP CUI_MENU_ITEM_INT_ACTION(CUI_MENU_ACTION_HELP_DESC, (CUI_pFnIntercept_t) CUI_menuActionHelp)
#define CUI_MENU_ITEM_BACK CUI_MENU_ITEM_ACTION(CUI_MENU_ACTION_BACK_DESC, (CUI_pFnAction_t) CUI_menuActionBack)
#define CUI_MAIN_MENU_END CUI_MENU_ITEM_HELP }};
#define CUI_SUB_MENU_END CUI_MENU_ITEM_BACK }};
#define CUI_MENU_ACTION_BACK_DESC  "<      BACK      >"
#define CUI_MENU_ACTION_HELP_DESC  "<      HELP      >"
#else

#define CUI_MAIN_MENU(_menuSymbol, _pMenuTitle, _numItems, _pMenuUpdateFn) \
        CUI_menu_t _menuSymbol;

#define CUI_SUB_MENU(_menuSymbol, _pMenuTitle, _numItems, _pUpperMenu) \
        CUI_menu_t _menuSymbol;

#define CUI_MENU_ITEM_SUBMENU(_pSubMenu)

#define CUI_MENU_ITEM_ACTION(_pItemDesc, _pFnAction)

#define CUI_MENU_ITEM_INT_ACTION(_pItemDesc, _pFnIntercept)

#define CUI_MENU_ITEM_LIST_ACTION(_pItemDesc, _maxListItems, _pFnListAction)

#define CUI_MENU_ITEM_HELP
#define CUI_MENU_ITEM_BACK
#define CUI_MAIN_MENU_END
#define CUI_SUB_MENU_END
#define CUI_MENU_ACTION_BACK_DESC  "<      BACK      >"
#define CUI_MENU_ACTION_HELP_DESC  "<      HELP      >"
#endif

#define CUI_IS_INPUT_NUM(_input)        ((_input >= '0') && (_input <= '9'))
#define CUI_IS_INPUT_ALPHA(_input)      ((_input >= 'a') && (_input <= 'z'))
#define CUI_IS_INPUT_ALPHA_NUM(_input)  ((CUI_IS_INPUT_ALPHA(_input)) && (CUI_IS_INPUT_NUM(_input)))
#define CUI_IS_INPUT_HEX(_input)        ((CUI_IS_INPUT_NUM(_input)) || ((_input >= 'a') && (_input <= 'f')))
#define CUI_IS_INPUT_BINARY(_input)     ((_input == '0') || (_input == '1'))



/* 预览可拦截项目的指示 */
#define CUI_ITEM_PREVIEW            0x00

/* 指示项目现在正在拦截uart */
#define CUI_ITEM_INTERCEPT_START    0xFE

/* 指示项目已完成拦截uart */
#define CUI_ITEM_INTERCEPT_STOP     0xFF

/* 指示项目拦截应该被取消 */
#define CUI_ITEM_INTERCEPT_CANCEL   0xF9

#define CUI_INPUT_UP                0xFA // 上箭头
#define CUI_INPUT_DOWN              0xFB // 下箭头
#define CUI_INPUT_RIGHT             0xFC // 右箭头
#define CUI_INPUT_LEFT              0xFD // 左箭头
#define CUI_INPUT_BACK              0x7F // 退格键
#define CUI_INPUT_EXECUTE           0x0D // 回车键
#define CUI_INPUT_ESC               0x1B // ESC(退出)键

#define CUI_COLOR_RESET             "\033[0m"
#define CUI_COLOR_RED               "\033[31m"
#define CUI_COLOR_GREEN             "\033[32m"
#define CUI_COLOR_YELLOW            "\033[33m"
#define CUI_COLOR_BLUE              "\033[34m"
#define CUI_COLOR_MAGENTA           "\033[35m"
#define CUI_COLOR_CYAN              "\033[36m"
#define CUI_COLOR_WHITE             "\033[37m"

#define CUI_DEBUG_MSG_START         "\0337"
#define CUI_DEBUG_MSG_END           "\0338"

/******************************************************************************
 * 类型定义
 */

/*
 * [返回类型]
 */
typedef enum CUI_retVal
{
  CUI_SUCCESS,
  CUI_FAILURE,
  CUI_INVALID_CB,
  CUI_RESOURCE_ALREADY_ACQUIRED,
  CUI_RESOURCE_NOT_ACQUIRED,
  CUI_MODULE_UNINITIALIZED,
  CUI_INVALID_CLIENT_HANDLE,
  CUI_MAX_CLIENTS_REACHED,
  CUI_NO_ASYNC_LINES_RELEASED,
  CUI_INVALID_LINE_ID,
  CUI_UNKOWN_VALUE_TYPE,
  CUI_UART_FAILURE,
  CUI_INVALID_PARAM,
  CUI_MAX_MENUS_REACHED,
  CUI_PREV_WRITE_UNFINISHED,
  CUI_MISSING_UART_UPDATE_FN,
  CUI_NOT_MANAGING_UART
} CUI_retVal_t;

/*
 * [通用CUI类型]
 */
typedef uint32_t CUI_clientHandle_t;

typedef struct {
    bool manageUart;
} CUI_params_t;

typedef struct {
    char clientName[MAX_CLIENT_NAME_LEN];
    uint8_t maxStatusLines;
} CUI_clientParams_t;

/*
 * [菜单相关类型]
 */
typedef struct {
    int16_t row;
    int16_t col;
} CUI_cursorInfo_t;

typedef void (*CUI_pFnClientMenuUpdate_t)(void);

/* 操作函数类型的类型定义 */
typedef void (*CUI_pFnAction_t)(const int32_t _itemEntry);
typedef void (*CUI_pFnIntercept_t)(const char _input, char* _lines[3], CUI_cursorInfo_t * _curInfo);
typedef void (*CUI_pFnListAction_t)(const uint32_t _listIndex, char* _lines[3], bool _selected);

typedef struct CUI_menu_s CUI_menu_t;
typedef struct CUI_list_s CUI_list_t;

typedef enum CUI_menuItems{
    CUI_MENU_ITEM_TYPE_SUBMENU,
    CUI_MENU_ITEM_TYPE_ACTION,
    CUI_MENU_ITEM_TYPE_INTERCEPT,
    CUI_MENU_ITEM_TYPE_LIST
}CUI_itemType_t;

/* 子菜单/操作项目条目的类型定义 */
typedef struct {
    char* pDesc;                      /* 操作描述。子菜单为NULL */
    CUI_itemType_t itemType;          /* 这是什么类型的菜单项 */
    bool interceptActive;             /* 项目当前是否被拦截 */
    union {
        CUI_menu_t* pSubMenu;               /* 子菜单 */
        CUI_pFnAction_t   pFnAction;        /* 操作函数 */
        CUI_pFnIntercept_t pFnIntercept;    /* 可拦截操作函数 */
        CUI_list_t* pList;                  /* 列表 */
    } item;
} CUI_menuItem_t;

/* 菜单对象的类型定义 */
struct CUI_menu_s {
    CUI_pFnClientMenuUpdate_t uartUpdateFn;     /* Uart更新函数 */
    const char* pTitle;                         /* 此菜单的标题 */
    uint8_t numItems;                           /* 项目条目数量 */
    CUI_menu_t*  pUpper;                        /* 上层菜单 */
    CUI_menuItem_t menuItems[];                 /* 项目条目 */
};

/* 列表对象的类型定义 */
struct CUI_list_s {
    CUI_pFnListAction_t pFnListAction;
    uint16_t maxListItems;
    uint16_t currListIndex;
};

/******************************************************************************
 函数原型
 *****************************************************************************/

/******************************************************************************
 * 通用CUI API
 *****************************************************************************/
/*********************************************************************
 * @fn          CUI_init
 *
 * @brief       初始化CUI模块。在调用任何其他CUI函数之前必须调用此函数。
 */
CUI_retVal_t CUI_init(CUI_params_t* _pParams);

/*********************************************************************
 * @fn          CUI_paramsInit
 *
 * @brief       将CUI_params_t结构初始化为已知状态。
 *                  在这种情况下，已知状态是将每个资源管理标志设置为true
 */
void CUI_paramsInit(CUI_params_t* _pParams);

/*********************************************************************
 * @fn          CUI_clientOpen
 *
 * @brief       使用CUI模块打开客户端。客户端需要请求/获取资源
 */
CUI_clientHandle_t CUI_clientOpen(CUI_clientParams_t* _pParams);

/*********************************************************************
 * @fn          CUI_clientParamsInit
 *
 * @brief       将CUI_clientParams_t结构初始化为已知状态。
 */
void CUI_clientParamsInit(CUI_clientParams_t* _pClientParams);

/*********************************************************************
 * @fn          CUI_close
 *
 * @brief       关闭CUI模块。释放所有资源和内存。
 */
CUI_retVal_t CUI_close();

/******************************************************************************
 * 菜单CUI API
 *****************************************************************************/
/*********************************************************************
 * @fn          CUI_registerMenu
 *
 * @brief       向CUI模块注册菜单
 */
CUI_retVal_t CUI_registerMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu);

/*********************************************************************
 * @fn          CUI_deRegisterMenu
 *
 * @brief       从CUI模块注销菜单
 */
CUI_retVal_t CUI_deRegisterMenu(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu);

/*********************************************************************
 * @fn          CUI_updateMultiMenuTitle
 *
 * @brief       更改默认多菜单标题
 */
CUI_retVal_t CUI_updateMultiMenuTitle(const char* _pTitle);

/*********************************************************************
 * @fn          CUI_menuNav
 *
 * @brief       导航到已注册菜单的特定条目
 */
CUI_retVal_t CUI_menuNav(const CUI_clientHandle_t _clientHandle, CUI_menu_t* _pMenu, const uint32_t _itemIndex);

/*********************************************************************
 * @fn          CUI_processMenuUpdate
 *
 * @brief       每当有UART输入需要处理时，应该调用此函数。
 */
CUI_retVal_t CUI_processMenuUpdate(void);

/******************************************************************************
 * 状态行CUI API
 *****************************************************************************/
/*********************************************************************
 * @fn          CUI_statusLineResourceRequest
 *
 * @brief       请求访问新状态行
 */
CUI_retVal_t CUI_statusLineResourceRequest(const CUI_clientHandle_t _clientHandle, const char _pLabel[MAX_STATUS_LINE_LABEL_LEN], const bool _refreshInd, uint32_t* _pLineId);

/*********************************************************************
 * @fn          CUI_statusLinePrintf
 *
 * @brief        更新已获取的状态行
 */
CUI_retVal_t CUI_statusLinePrintf(const CUI_clientHandle_t _clientHandle, const uint32_t _lineId, const char *format, ...);

void CUI_wrappedIncrement(size_t* _pValue, int32_t _incAmt, size_t _maxValue);
/*********************************************************************
 * 断言调试API
 ********************************************************************/
/*********************************************************************
 *  @fn         CUI_assert
 *
 *  @brief      无需cuiHandle_t，您可以打印断言字符串，并可选择在闪烁LED时自旋锁定。
 *
 *              注意：如果您选择自旋锁定，此函数将关闭所有已打开的现有客户端，然后进入闪烁LED的无限循环。
 */
void CUI_assert(const char* _assertMsg, const bool _spinLock);
#ifndef CUI_MIN_FOOTPRINT
void CUI_menuActionBack(const int32_t _itemEntry);
void CUI_menuActionHelp(const char _input, char* _pLines[3], CUI_cursorInfo_t* _pCurInfo);
#endif
#ifdef __cplusplus
}
#endif

#endif /* CUI_H */
